﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Cms.Core.AspNetMvc;
using Cms.Core.Infrastructure.Helps;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Cors.Internal;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.PlatformAbstractions;
using Swashbuckle.AspNetCore.Swagger;
using Swashbuckle.AspNetCore.SwaggerGen;
using Cms.Core.Infrastructure.Extension;
using Swashbuckle.AspNetCore.SwaggerUI;
using Microsoft.Extensions.Caching.Memory;
using Cms.Core.AspNetMvc.Mvc.Middleware;
using Cms.Core.AspNetMvc.Mvc.Filter;
using Cms.Core.Data.UnitOfWork;
using Cms.Core.Infrastructure.AutoMapper;
using Microsoft.AspNetCore.Http;
using Cms.Core.Infrastructure.Common;
using log4net;
using log4net.Config;
using log4net.Repository;
using Cms.Core.Infrastructure.Log;
using Cms.Core.Infrastructure.Dependency;
using Cms.Core.Application.IService;
using Cms.Core.Application.Service;
using Cms.Core.Infrastructure.Extensions;
using System.Text;
using Microsoft.IdentityModel.Tokens;
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using System.IdentityModel.Tokens.Jwt;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Cms.Core.Web.AuthHelper;
using Cms.Core.Infrastructure.Cache;
using Cms.Core.Infrastructure.AuthHelper;
using Cms.Core.Application.Secutiry.AuthHelper;

using Cms.Core.Validation.FluentValidations;
using Cms.Core.DynamicWebApi;

namespace Cms.Core.Web
{
    public class Startup
    {

        private readonly string _currAssemblyName = typeof(Startup).Assembly.GetName().Name;
        public readonly string _apiName = "Cms.Core";
        public static ILoggerRepository repository { get; set; }
        private readonly ICacheBase _cacheBase = null;


        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
            repository = LogManager.CreateRepository(Keys.log4netRepositoryName);
            XmlConfigurator.Configure(repository, new FileInfo("log4net.config"));

        }

        public IConfiguration Configuration { get; }

        // 此方法由运行时调用。使用此方法将服务添加到容器.
        public void ConfigureServices(IServiceCollection services)
        {

            #region jwt相关信息
            var jwtSettings = Configuration.GetSection("JwtSettings");
            var symmetricKeyAsBase64 = jwtSettings["SecretKey"];
            var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64);
            var signingKey = new SymmetricSecurityKey(keyByteArray);

            var tokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = signingKey,
                ValidateIssuer = true,
                ValidIssuer = jwtSettings["Issuer"],//发行人
                ValidateAudience = true,
                ValidAudience = jwtSettings["Audience"],//订阅人
                ValidateLifetime = true,    ////是否验证Token有效期，使用当前时间与Token的Claims中的NotBefore和Expires对比
                ClockSkew = TimeSpan.Zero, ////允许的服务器时间偏移量
                RequireExpirationTime = true,

                ValidateTokenReplay = true,

                //LifetimeValidator = (before, expires, token, param)=>{

                //    return expires > DateTime.Now;
                //},
            };

            var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256);

            // 如果想写死，可以直接在这里写。
            var permissionItem = new List<PermissionItem>();

            //// 如果要数据库动态绑定，这里先留个空，后边处理器里动态赋值
            //var permissionItem = new List<PermissionItem>();
            // 角色与接口的权限要求参数
            var permissionRequirement = new PermissionRequirement(
             "/api/denied",// 拒绝授权的跳转地址（目前无用）
             permissionItem,//这里还记得么，就是我们上边说到的角色地址信息凭据实体类 PermissionItem
             ClaimTypes.Role,//基于角色的授权
             jwtSettings["Issuer"],//发行人
             jwtSettings["Audience"],//订阅人
             signingCredentials,//签名凭据
             expiration: TimeSpan.FromDays(7),//接口的过期时间，注意这里没有了缓冲时间，你也可以自定义，在上边的TokenValidationParameters的 ClockSkew
             anonymousUrlList: new List<string>()
             );
            permissionRequirement.LoginPath = "/api/Login/LoginAsync";
            //List<string> list = new List<string>();
            //list.Add("/api/Audit/GetAuditPageDataAsync");
            //list.Add("/api/Values");
            //list.Add("/api/v1/Default");
            //permissionRequirement.AnonymousUrlList.AddRange(list);
            //1.重点配置授权服务，也就是具体的规则，已经对应的权限策略，比如公司不同权限的门禁卡
            //services.AddAuthorization(options =>
            //{
            //    options.AddPolicy("Permission", policy => policy.Requirements.Add(permissionRequirement));
            //})
            //2.重点必需要配置认证服务，这里是jwtBearer默认认证，比如光有卡没用，得能识别他们
            services.AddAuthentication(x =>
            {
                x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                //x.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
                x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })
            // 3.重点，针对JWT的配置，比如门禁是如何识别的，是放射卡，还是磁卡
            .AddJwtBearer(o =>
            {
                o.SecurityTokenValidators.Clear();
                o.SecurityTokenValidators.Add(new CmsJwtSecurityTokenHandler());
                o.SaveToken = true;
                o.TokenValidationParameters = tokenValidationParameters;

                o.Events = new JwtBearerEvents /*jwt自带事件*/
                {
                    OnAuthenticationFailed = context =>
                    {



                        // 如果过期，则把<是否过期>添加到，返回头信息中
                        if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
                        {
                            context.Response.Headers.Add("Token-Expired", "true");
                        }
                        return Task.CompletedTask;
                    },
                    OnTokenValidated = context =>
                    {
                        if ((context.Principal?.Identity?.IsAuthenticated).AsTo<bool>())
                        {
                            context.HttpContext.User = context.Principal;
                        }
                        return Task.CompletedTask;

                    }


                };
            });
            //JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

            //services.AddAuthentication("Bearer")
            //  .AddIdentityServerAuthentication(options =>
            //  {
            //      options.Authority = "http://localhost:5000";//授权服务器地址
            //      options.RequireHttpsMetadata = false;//是否Https
            //      options.ApiName = "cms.admin vue";
            //      options.JwtBearerEvents = new JwtBearerEvents
            //      {
            //          OnTokenValidated = context =>
            //          {

            //              if ((context.Principal?.Identity?.IsAuthenticated).AsTo<bool>())
            //              {
            //                  context.HttpContext.User = context.Principal;
            //              }

            //              return Task.CompletedTask;

            //          }

            //      };
            //  });
            //注入授权Handler

            #endregion
            #region 缓存
            services.AddMemoryCache();
            #endregion
            services.AddMvc(options =>
            {

                options.Filters.Add(new AdminAuthorizeFilter());
                options.Filters.Add(new AuditActionFilter());
                options.Filters.Insert(0, new AsyncGlobalExceptionsFilter());
                //options.Filters.Add(new AsyncAuditActionFilter());//审计暂时注掉
                options.Filters.Add(new CorsAuthorizationFilterFactory(Keys.defaultCorsPolicyName));
                //options.Filters.Add(new AuditActionParameter());
            }).SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
           // 取消默认驼峰
           .AddJsonOptions(options => { options.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver(); })
           .ConfigureApiBehaviorOptions(options =>
           {
               options.SuppressConsumesConstraintForFormFileParameters = true;
               options.SuppressInferBindingSourcesForParameters = true;
               options.SuppressModelStateInvalidFilter = true;
               options.SuppressMapClientErrors = true;


           }).AddValidators(services);

            #region CORS
            //跨域第二种方法，声明策略，记得下边app中配置
            services.AddCors(c =>
            {
                //注意正式环境不要使用这种全开放的处理
                //c.AddPolicy(Keys.defaultCorsPolicyName, policy =>
                //{
                //    policy.WithOrigins(Configuration["Application:CorsOrigins"]
                //      .Split(",", StringSplitOptions.RemoveEmptyEntries).ToArray())//支持多个域名端口，注意端口号后不要带/斜杆：比如localhost:8000/，是错的
                //  .AllowAnyOrigin()//允许任何源
                //    .AllowAnyMethod()//允许任何方式
                //    .AllowAnyHeader()//允许任何头
                //    .AllowCredentials();//允许cookie
                //});
                //注意正式环境不要使用这种全开放的处理
                //一般采用这种方法
                c.AddPolicy(Keys.defaultCorsPolicyName, policy =>
                {
                    policy.WithOrigins(Configuration["Application:CorsOrigins"]
                        .Split(",", StringSplitOptions.RemoveEmptyEntries).ToArray())//支持多个域名端口，注意端口号后不要带/斜杆：比如localhost:8000/，是错的
                    .AllowAnyHeader()//Ensures that the policy allows any header.
                    .AllowAnyMethod()
                    .AllowCredentials();//允许cookie
                });
            });

            //跨域第一种办法，注意下边 Configure 中进行配置
            //services.AddCors();

            #endregion
            #region Swagger
            //services.AddSwaggerGen(options =>
            //{
            //    options.SwaggerDoc("v1", new Info { Title = "Panda Dynamic WebApi", Version = "v1" });

            //    // TODO:一定要返回true！
            //    options.DocInclusionPredicate((docName, description) =>
            //    {
            //        return true;
            //    });

            //    options.IncludeXmlComments(@"bin\Debug\netcoreapp2.2\Cms.Core.Web.xml");
            //});

            services.AddSwaggerGen(gen => this.CreateSwaggerGen(gen));
            #endregion

            #region 注入上下文，注入工作单元，注入仓储
            services.AddDbContext<UnitOfWorkDbContext>(Configuration.GetConnectionString("DefaultConnection"));

            services.AddUnitOfWork<UnitOfWorkDbContext>();
            services.AddRepository();

            #endregion

            #region automapper
            AutoMapperHelper.Register();  //创建automapper自己映射完毕后进行注册
            #endregion
            #region 所有DI注入
            services.AddSingleton<ICacheBase, CustomMemoryCache>();
            //services.AddScoped(typeof(ScopedDictionary),typeof(ScopedDictionary));
            services.AddSingleton<IMemoryCache>(factory =>
            {
                var cache = new MemoryCache(new MemoryCacheOptions());
                return cache;
            });
            services.AddSingleton<MyMemoryCache>();
            services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
            services.AddSingleton<IScopedServiceResolver, RequestScopedServiceResolver>(); //应该用动态注入
            services.AddScoped<ScopedDictionary>();
            //services.AddScoped<Application.Secutiry.IOnlineUserCache, OnlineUserCache>();
            //services.AddScoped<IOnlineUserProvider, OnlineUserProvider>();
            //services.AddScoped<IOnlineTokenCache, OnlineTokenCache>();
            //系统所有需要自动注入的DI
            services.AddCustomServices();
            //services.AddSingleton<IAuthorizationHandler, PermissionHandler>();
            services.AddSingleton(permissionRequirement);
            #endregion

            services.AddDynamicWebApi();

        }

        // 此方法由运行时调用。使用此方法配置HTTP请求管道.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            #region Environment
            if (env.IsDevelopment())
            {
                // 在开发环境中，使用异常页面，这样可以暴露错误堆栈信息，所以不要放在生产环境。
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                // 在非开发环境中，使用HTTP严格安全传输(or HSTS) 对于保护web安全是非常重要的。
                // 强制实施 HTTPS 在 ASP.NET Core，配合 app.UseHttpsRedirection
                //app.UseHsts();

            }
            #endregion
            app.UseErrorHandling();



            //app.UseMvcWithDefaultRoute();
            #region Swagger

            app.UseSwagger();

            app.UseSwaggerUI(ui => this.CreateSwaggerUI(ui));
            //app.UseSwaggerUI(c =>
            //{
            //    c.SwaggerEndpoint("/swagger/v1/swagger.json", "Panda Dynamic WebApi");
            //});
            #endregion
            //使用官网认证，必须加上。
            app.UseAuthentication();
            //这代码一定要在MVC上面
            app.UseCors(Keys.defaultCorsPolicyName);


            // 跳转https
            app.UseHttpsRedirection();
            // 使用静态文件
            app.UseStaticFiles();
            // 使用cookie
            app.UseCookiePolicy();
            // 返回错误码
            app.UseStatusCodePages();//把错误码返回前台，比如是404
            app.UseMvc();
        }


        /// <summary>
        /// 得到所有自定义路由特性
        /// </summary>
        /// <returns></returns>
        private List<CustomRouteAttribute> GetAllCustomRouteAttributes()
        {



            var key = "_Custom_Route_Cache";
            if (MyMemoryCache.Get(key).IsNull())
            {
                var list = RuntimeHelper.GetTypesByAssembly(_currAssemblyName).Where(o => o.HasAttribute<CustomRouteAttribute>()).Select(o => o.GetCustomAttribute<CustomRouteAttribute>()).ToList();
                MyMemoryCache.Set(key, list);
                return list;
            }
            //应该加入缓存
            return MyMemoryCache.Get<List<CustomRouteAttribute>>(key);
        }

        private void CreateSwaggerGen(SwaggerGenOptions genOptions)
        {
            var basePath = PlatformServices.Default.Application.ApplicationBasePath;
         
            this.GetAllCustomRouteAttributes().ForEach(version =>
            {

                genOptions.SwaggerDoc(version.GroupName, new Info
                {
                    // {ApiName} 定义成全局变量，方便修改
                    Version = version.GroupName,
                    Title = $"{_apiName}接口文档",
                    Description = $"{_apiName} HTTP API {version.GroupName}",
                    TermsOfService = "None",
                    Contact = new Contact { Name = "大黄瓜", Email = "179722134@qq.com", Url = "" }
                });
                // 按相对路径排序
                genOptions.OrderActionsBy(o => o.RelativePath);
   
            });

            // TODO:一定要返回true！
            genOptions.DocInclusionPredicate((docName, description) =>
            {
                return true;
            });

            var xmlPath = Path.Combine(basePath, "Cms.Core.Web.xml");
            genOptions.IncludeXmlComments(xmlPath, true);

            var xmlApplicationPath = Path.Combine(basePath, "Cms.Core.Application.xml");
            genOptions.IncludeXmlComments(xmlApplicationPath, true);

            genOptions.AddSecurityDefinition("Bearer", new ApiKeyScheme
            {
                Description = "JWT授权(数据将在请求头中进行传输) 直接在下框中输入Bearer {token}（注意两者之间是一个空格）\"",
                Name = "Authorization",//jwt默认的参数名称
                In = "header",//jwt默认存放Authorization信息的位置(请求头中)
                Type = "apiKey"
            });

            genOptions.AddSecurityRequirement(new Dictionary<string, IEnumerable<string>>
                {
                    { "Bearer", new string[] { } }
                });

         
        }

        public void CreateSwaggerUI(SwaggerUIOptions uiOptions)
        {

            this.GetAllCustomRouteAttributes().ForEach(v =>
            {
                CreateSwaggerUIAction(uiOptions, v, _apiName);
            });

            //uiOptions.IndexStream = () => GetType().GetTypeInfo().Assembly.GetManifestResourceStream($"{_currAssemblyName}.index.html");
            uiOptions.RoutePrefix = string.Empty; //路径配置，设置为空，表示直接在根域名（localhost:8002）访问该文件,注意localhost:8002/swagger是访问不到的，去launchSettings.json把launchUrl去掉
        }

        private Action<SwaggerUIOptions, CustomRouteAttribute, string> CreateSwaggerUIAction = (sui, v, apiName) =>
        {
            sui.SwaggerEndpoint($"/swagger/{v.GroupName}/swagger.json", $"{apiName} {v.GroupName}");

        };
    }

    ///// <summary>
    ///// 缓存，暂时这样，日后封装
    ///// </summary>
    public sealed class MyMemoryCache
    {
        private static MemoryCache Cache { get; set; }


        static MyMemoryCache()
        {
            Cache = new MemoryCache(new MemoryCacheOptions
            {

            });
        }

        /// <summary>
        /// 得到缓存
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <returns></returns>
        public static T Get<T>(string key) where T : class, new() => Cache.Get<T>(key);

        public static void Remove(object key) => Cache.Remove(key);
        public static bool TryGetValue(object key, out object value)
        {
            return Cache.TryGetValue(key, out value);
        }
        public static object Get(object key) => Cache.Get(key);

        public static void Set<T>(string key, T t) where T : class, new() => Cache.Set<T>(key, t);
    }
}
